﻿using DryIoc;
using HslCommunication;
using HslCommunication.Profinet.Siemens;
using Newtonsoft.Json.Linq;
using NPOI.SS.Formula.Functions;
using Prism.Events;
using Prism.Mvvm;
using Prism.Services.Dialogs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ZoneLineNew.Common;
using ZoneLineNew.Core;
using ZoneLineNew.Core.EventArgs;
using ZoneLineNew.Enums;
using ZoneLineNew.Models;
using ZoneLineNew.Models.Entity;
using ZoneLineNew.Models.Entity.StationResult;
using ZoneLineNew.Models.Entity.Template;
using ZoneLineNew.Models.PrintResult;
using ZoneLineNew.Models.StationResult.HOPStation;
using ZoneLineNew.Models.StationResult.QOPStation;
using ZoneLineNew.Services.DB;
using ZoneLineNew.Services.Print;

namespace ZoneLineNew.Services.Station {
    /// <summary>
    /// 工站基类服务
    /// </summary>
    /// <typeparam name="T">StationResultBase的子类，过程数据信息</typeparam>
    /// <typeparam name="T2">StationConfigBase的子类，工位配置信息</typeparam>
    public abstract class WorkStationBaseService<T, T2> : BindableBase, IWorkStationService<T, T2>
        where T : StationResultBase, new() where T2 : StationConfigBase, new() {

        protected const string _okCode = "OK";
        protected const string _ngCode = "NG";
        protected string _logUiChannel = "debug";
        protected SiemensS7Net _client;
        protected readonly IDbOperate<IFreeSqlMainContext> _mainDbOperate;
        protected readonly IEventAggregator _eventAggregator;
        protected readonly PrinterLabelService _printerService;

        protected CancellationToken _cancellationToken;

        static WorkStationBaseService() {
            HslCommunication.Authorization.SetAuthorizationCode("b980977c-3323-4876-b633-c0bef93d75c1");
        }

        public WorkStationBaseService(
            IDbOperate<IFreeSqlMainContext> mainDbOperate,
            IEventAggregator eventAggregator,
            PrinterLabelService printerService
            ) {
            this._mainDbOperate = mainDbOperate;

            this._eventAggregator = eventAggregator;

            this._printerService = printerService;

            this.StationConfig = _mainDbOperate.GetModel<T2>(o => true); //这句必须在前面，因为后面日志通道要用到站点名称

            this._logUiChannel = CurrentStationName;

            _cancellationToken = new CancellationToken();
        }

        /// <summary>
        /// 站点名称
        /// </summary>
        public string CurrentStationName => StationConfig?.StationCode;

        /// <summary>
        /// IP
        /// </summary>
        public string IP => StationConfig?.Ip;

        /// <summary>
        /// 端口
        /// </summary>
        public int Prot => StationConfig.Port;

        private bool _isConnected;
        /// <summary>
        /// 是否连接
        /// </summary>
        public bool IsConnected {
            get { return _isConnected; }
            private set { _isConnected = value; base.RaisePropertyChanged(); }
        }

        /// <summary>
        /// 站点信息配置
        /// </summary>
        protected T2 _stationConfig = new T2();
        /// <summary>
        /// 站点结果数据
        /// </summary>
        public virtual T2 StationConfig {
            get => _stationConfig;
            set {
                _stationConfig = value;
                base.RaisePropertyChanged();
            }
        }

        protected T _stationRecordData = new T();
        /// <summary>
        /// 站点结果数据
        /// </summary>
        public virtual T StationRecordData {
            get => _stationRecordData;
            set {
                _stationRecordData = value;
                base.RaisePropertyChanged();
            }
        }


        private int _currentLableIndex;
        /// <summary>
        /// 当前流水号
        /// </summary>
        public int CurrentLableIndex {
            get { return _currentLableIndex; }
            set { _currentLableIndex = value; base.RaisePropertyChanged(); }
        }


        #region PLC Common Props

        private bool _heart;

        public bool Heart {
            get { return _heart; }
            protected set => SetProperty(ref _heart, value);
        }

        private bool _startUploadCode;

        public bool StartUploadCode {
            get { return _startUploadCode; }
            protected set {
                SetProperty(ref _startUploadCode, value);
            }
        }

        private bool _startUploadData;

        public bool StartUploadData {
            get { return _startUploadData; }
            protected set {
                SetProperty(ref _startUploadData, value);
            }
        }

        private bool _heartToPlc;

        public bool HeartToPlc {
            get { return _heartToPlc; }
            protected set => SetProperty(ref _heartToPlc, value);
        }

        private bool _endUpLoadCode;

        public bool EndUpLoadCode {
            get { return _endUpLoadCode; }
            protected set {
                SetProperty(ref _endUpLoadCode, value);
            }
        }


        private bool _endUpLoadData;

        public bool EndUpLoadData {
            get { return _endUpLoadData; }
            protected set {
                SetProperty(ref _endUpLoadData, value);
            }
        }


        private int _allowWork;

        public int AllowWork {
            get { return _allowWork; }
            protected set => SetProperty(ref _allowWork, value);
        }

        #endregion

        public bool Connect() {

            var task = Task.Factory.StartNew(async () => {
                try {
                    while (!_cancellationToken.IsCancellationRequested) {

                        HslCommunication.OperateResult operateResult = new HslCommunication.OperateResult();
                        if (this.StationConfig != null && !IsConnected) {
                            if (_client == null) {
                                _client = new SiemensS7Net(SiemensPLCS.S1200);
                                _client.IpAddress = this.StationConfig.Ip;
                                _client.Port = this.StationConfig.Port;
                                //_client.SetPersistentConnection();
                                _client.ConnectTimeOut = 3 * 1000;
                                PropertyChanged += WorkStationBaseService_PropertyChanged;
                                operateResult = _client.ConnectServer();
                            }
                            if (!IsConnected) {
                                LogWithUi("正在尝试重新连接....");
                                operateResult = _client.ConnectServer();
                            }
                            IsConnected = operateResult.IsSuccess;
                        }

                        if (IsConnected) {

                            //1.写入心跳
                            WriteData(this.StationConfig.HeartToPlcAddr, HeartToPlc == true ? (short)0 : (short)1, isRepeat: true, isHeart: true);

                            //2.解析数据
                            var readRlt = _client.Read(this.StationConfig.ReadStartAddr, this.StationConfig.ReadLength);
                            if (readRlt.IsSuccess) {

                                //要先解析数据，在解析控制信号，再信号处理
                                DealData(readRlt.Content);
                                DealWithCommonData(readRlt.Content);
                                //DealWithSignal();

                            } else {
                                IsConnected = false;
                            }

                        } else {
                            LogWithUi(operateResult.Message);
                        }

                        await Task.Delay(100);//
                    }

                } catch (Exception ex) {
                    LogWithUi(ex.Message);
                }

            }, _cancellationToken);

            return IsConnected;
        }

        public SiemensS7Net GetSiemensS7Client() {
            return _client;
        }

        public abstract void DealData(byte[] content);

        public abstract void DealWithCommonData(byte[] content);

        public abstract void DealWithSignal(string signalName);

        public abstract int RecordData();

        /// <summary>
        /// 验证
        /// </summary>
        /// <param name="stationName"></param>
        /// <param name="code"></param>
        /// <param name="otherStations"></param>
        /// <returns></returns>
        protected virtual WorkActionEnum Validata(string stationName, string code, string[] otherStations = null) {
            var currentStationName = stationName;
            var stationIndex = Cache.WorkStationValidationList.IndexOf(currentStationName);
            if (stationIndex == -1) {
                return WorkActionEnum.NoEnable;
            }
            if (stationIndex > 0) {
                //检测上一个站
                var beforeStationName = Cache.WorkStationValidationList[stationIndex - 1];
                LogWithUi($"在{currentStationName}工位上一个启动的检查工位是{beforeStationName}", code);
                var checkRlt = CheckCodeExistAndResult(beforeStationName, code);
                LogWithUi($"在{beforeStationName}工位检查{code}的结论是{checkRlt}", code);
                if (checkRlt == 1) {
                    if (otherStations != null) {
                        LogWithUi($"在上个工位{beforeStationName}工位检查{code}的结论是{checkRlt},开始检查校验工位结论", code);
                        foreach (var otherStation in otherStations) {
                            LogWithUi($"{currentStationName}检查校验工位{otherStation}是否工作", code);
                            var otherStationResult = CheckCodeExistAndResult(otherStation, code);
                            if (otherStationResult == 1) {
                                LogWithUi($"{currentStationName}检查校验工位{otherStation}工作过，不在本工位进行工作", code);
                                return WorkActionEnum.WorkOkInCheckStation;
                            }
                        }
                    }
                    LogWithUi($"在上个工位{beforeStationName}工位检查{code}的结论是{checkRlt},开始检查是否在本工位{currentStationName}结论", code);
                    var currentResult = CheckCodeExistAndResult(currentStationName, code);
                    LogWithUi($"在本工位{currentStationName}工位检查{code}的结论是{currentResult}", code);
                    if (currentResult == 1) {
                        return WorkActionEnum.HasWorkInCurrent;
                    } else {
                        return WorkActionEnum.StartWork;
                    }
                } else if (checkRlt == 2) {
                    return WorkActionEnum.WorkNgInBefore;
                } else {
                    return WorkActionEnum.NotWorkInBefore;
                }
            } else {
                //检查当前工位
                if (otherStations != null) {
                    LogWithUi($"在{currentStationName}工位不存在上个工位，检查并行工位工作结论", code);

                    foreach (var otherStation in otherStations) {
                        LogWithUi($"{currentStationName}检查校验工位{otherStation}是否工作", code);
                        var otherStationResult = CheckCodeExistAndResult(otherStation, code);
                        if (otherStationResult == 1) {
                            LogWithUi($"{currentStationName}检查校验工位{otherStation}工作过，不在本工位进行工作", code);
                            return WorkActionEnum.WorkOkInCheckStation;
                        }
                    }
                }
                LogWithUi($"在{currentStationName}工位不存在上个工位，检查在本工位工作结论", code);
                var checkResult = CheckCodeExistAndResult(currentStationName, code);
                if (checkResult == 1) {
                    return WorkActionEnum.HasWorkInCurrent;
                } else {
                    return WorkActionEnum.StartWork;
                }
            }
        }

        /// <summary>
        /// 检查该工件在这个工位是否加工以及加工结论
        /// </summary>
        /// <param name="stationCode"></param>
        /// <param name="code"></param>
        /// <returns>0，表示未加工作，1表示加工OK，2表示加工但是加工NG</returns>
        /// <exception cref="Exception"></exception>
        protected virtual int CheckCodeExistAndResult(string stationCode, string code) {
            switch (stationCode) {
                //todo 补充所有的工位
                case "OP550": {
                        var op006Result = _mainDbOperate.GetModelList<QOP006StationResult>(p => p.AbsScanCode == code).OrderByDescending(o => o.CreateTime).FirstOrDefault();
                        if (op006Result == null) {
                            return 0;
                        } else if (op006Result.IsReworked) {
                            LogWithUi($"在{stationCode}本工位检验，被标记为返工件", code);
                            return 0;
                        } else if (op006Result.Result == _okCode) {
                            return 1;
                        } else {
                            return 2;
                        }
                    }
                case "OP560":
                    var op020Result = _mainDbOperate.GetModelList<QOP020StationResult>(p => p.AbsScanCode == code).OrderByDescending(o => o.CreateTime).FirstOrDefault();
                    if (op020Result == null) {
                        return 0;
                    } else if (op020Result.IsReworked) {
                        LogWithUi($"在{stationCode}本工位检验，被标记为返工件", code);
                        return 0;
                    } else if (op020Result.Result == _okCode) {
                        return 1;
                    } else {
                        return 2;
                    }
                case "OP570": {
                        var op010Result = _mainDbOperate.GetModelList<QOP010StationResult>(p => p.AbsScanCode == code).OrderByDescending(o => o.CreateTime).FirstOrDefault();
                        if (op010Result == null) {
                            return 0;
                        } else if (op010Result.IsReworked) {
                            LogWithUi($"在{stationCode}本工位检验，被标记为返工件", code);
                            return 0;
                        } else if (op010Result.Result == _okCode) {
                            return 1;
                        } else {
                            return 2;
                        }
                    }
                case "OP580-1":
                case "OP580-2":
                    var op050LResult = _mainDbOperate.GetModelList<QOP050LStationResult>(p => p.AbsScanCode == code).OrderByDescending(o => o.CreateTime).FirstOrDefault(); ;
                    var op050RResult = _mainDbOperate.GetModelList<QOP050RStationResult>(p => p.AbsScanCode == code).OrderByDescending(o => o.CreateTime).FirstOrDefault(); ;
                    if (op050LResult == null && op050RResult == null) {
                        return 0;
                    } else if ((op050LResult != null && op050LResult.IsReworked)) {
                        LogWithUi($"在{stationCode}本工位检验，OP580-1被标记为返工件", code);
                        return 0;
                    } else if ((op050RResult != null && op050RResult.IsReworked)) {
                        LogWithUi($"在{stationCode}本工位检验，OP580-2被标记为返工件", code);
                        return 0;
                    } else if ((op050LResult != null && op050LResult.Result == _okCode) || (op050RResult != null && op050RResult.Result == _okCode)) {
                        return 1;
                    } else {
                        return 2;
                    }
                case "OP620":
                    var hOp010Result = _mainDbOperate.GetModelList<HOP010StationResult>(p => p.AbsScanCode == code).OrderByDescending(o => o.CreateTime).FirstOrDefault();
                    if (hOp010Result == null) {
                        return 0;
                    } else if (hOp010Result.IsReworked) {
                        LogWithUi($"在{stationCode}本工位检验，被标记为返工件", code);
                        return 0;
                    } else if (hOp010Result.Result == _okCode) {
                        return 1;
                    } else {
                        return 2;
                    }
                case "OP640":
                    var hOp040Result = _mainDbOperate.GetModelList<HOP040StationResult>(p => p.AbsScanCode == code).OrderByDescending(o => o.CreateTime).FirstOrDefault();
                    if (hOp040Result == null) {
                        return 0;
                    } else if (hOp040Result.IsReworked) {
                        LogWithUi($"在{stationCode}本工位检验，被标记为返工件", code);
                        return 0;
                    } else if (hOp040Result.Result == _okCode) {
                        return 1;
                    } else {
                        return 2;
                    }
                //break;
                default:
                    throw new Exception("not exist station");
            }
        }

        /// <summary>
        /// 主码验证
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        protected virtual bool MainCodeVerify(string code) {
            bool checkRlt = true;
            if (AppSession.Config.IsEnabledCylinderCodeVerify) {
                string rulesCode = Cache.CurWorkProduct.ValidationRules;
                if (rulesCode != null) {
                    //TODO
                    checkRlt = ValidationHelper.ValidateWithPattern(code, rulesCode);
                }
                checkRlt = false;
            } else {
                LogWithUi($"在{CurrentStationName}工位【未启用】检查缸体码{code}", code);
            }
            return checkRlt;
        }

        /// <summary>
        /// 初始化复位地址
        /// </summary>
        /// <returns></returns>
        protected void InitReSetAddr() {
            _ = Task.Run(async () => {
                while (!IsConnected) {
                    //LogWithUi($"在{CurrentStationName}正在等待设备连接，结果：{IsConnected}");
                    await Task.Delay(1000);
                }
                var res1 = WriteData(StationConfig.EndUploadCodeAddr, 0, isRepeat: true);
                LogWithUi($"在{CurrentStationName}初始化，复位上传条码完成指令，结果：{res1}");
                var res2 = WriteData(StationConfig.AllowWorkAddr, 0, isRepeat: true);
                LogWithUi($"在{CurrentStationName}初始化，复位工作控制指令，结果：{res2}");
                var res3 = WriteData(StationConfig.EndUploadDataAddr, 0, isRepeat: true);
                LogWithUi($"在{CurrentStationName}初始化，复位数据记录指令，结果：{res3}");
            });
        }

        /// <summary>
        /// Log滚动
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="absScanCode"></param>
        /// <param name="channel"></param>
        protected void LogWithUi(string msg, string absScanCode = "", string channel = "") {

            if (channel == "") {
                channel = this._logUiChannel;
            }

            if (!string.IsNullOrEmpty(absScanCode)) {
                msg = $"产品{absScanCode}，{msg}";
            }

            LogManager.Info(msg);

            var args = new MessageData() {
                Code = channel,
                Parameters = new DialogParameters() {
                    { "Message",msg }
                }
            };
            _eventAggregator.GetEvent<PubSubEventArgs>().Publish(args);

        }


        /// <summary>
        /// 写入工作指令
        /// </summary>
        /// <param name="addr"></param>
        /// <param name="work"></param>
        /// <param name="code"></param>
        /// <param name="isRepeat"></param>
        /// <returns></returns>
        protected virtual bool WriteWorkActionToPlc(string addr, WorkActionEnum work, string code = "", bool isRepeat = false) {
            return WriteData(addr, (short)work, code, isRepeat);
        }


        /// <summary>
        /// 向PLC写入【short】数据
        /// </summary>
        /// <param name="addr">地址</param>
        /// <param name="value">值</param>
        /// <param name="code">条码</param>
        /// <param name="isRepeat">是否重发</param>
        /// <param name="isHeart">是否是心跳</param>
        /// <returns></returns>
        protected bool WriteData(string addr, short value, string code = "", bool isRepeat = false, bool isHeart = false) {

            if (string.IsNullOrEmpty(addr)) {
                return false;
            }

            if (_client != null && IsConnected) {
                var rlt = _client.Write(addr, value).IsSuccess;

                LogManager.Debug($"{_logUiChannel}{(code != "" ? ("，产品：" + code) : "")}，在{CurrentStationName}工位下发数据，{(isHeart ? "MES心跳" : "")}地址：{addr}，值：{value}，下发结果：{rlt}");

                var readValue = _client.ReadInt16(addr).Content;

                LogManager.Debug($"{_logUiChannel}{(code != "" ? ("，产品：" + code) : "")}，在{CurrentStationName}工位下发数据后，重新读取{(isHeart ? "MES心跳" : "")}地址：{addr}，值：{readValue}");

                if (isRepeat && value != readValue) {

                    LogWithUi($"在{CurrentStationName}工位下发数据，{(isHeart ? "MES心跳" : "")}地址：{addr}，下发值：{value}，读取到的值：{readValue}，信号通信发送存在延迟，正在重试", code);

                    LogManager.Error($"{_logUiChannel}{(code != "" ? ("，产品：" + code) : "")}，在{CurrentStationName}工位下发数据，下发{(isHeart ? "MES心跳" : "")}地址：{addr}，下发值：{value}，读取到的值：{readValue}，信号通信发送存在延迟，正在重试");

                    while (value != readValue) {

                        LogWithUi($"由于网络正在重新下发指令", code);

                        LogManager.Error($"{_logUiChannel}{(code != "" ? ("，产品：" + code) : "")}，在{CurrentStationName}工位，由于网络正在重新下发!");

                        rlt = _client.Write(addr, value).IsSuccess;

                        readValue = _client.ReadInt16(addr).Content;

                        System.Threading.Thread.Sleep(500);
                    }
                }
                return rlt;

            } else {
                LogWithUi($"设备未连接，发送指令失败", code);
            }
            return false;
        }

        /// <summary>
        /// 向PLC写入【string】数据
        /// </summary>
        /// <param name="addr">地址</param>
        /// <param name="value">值</param>
        /// <param name="code">条码</param>
        /// <param name="isRepeat">是否重发</param>
        /// <param name="isHeart">是否是心跳</param>
        /// <returns></returns>
        protected bool WriteData(string addr, string value, string code = "", bool isRepeat = false) {

            if (string.IsNullOrEmpty(addr)) {
                return false;
            }

            if (_client != null && IsConnected) {

                var rlt = _client.Write(addr, value).IsSuccess;

                LogManager.Debug($"{_logUiChannel}{(code != "" ? ("，产品：" + code) : "")}，在{CurrentStationName}工位下发数据，地址：{addr}，下发值：{value}，下发结果：{rlt}");

                var readValue = _client.ReadString(addr).Content;

                if (isRepeat && value != readValue) {

                    LogWithUi($"在{CurrentStationName}工位下发数据，地址：{addr}，下发值：{value}，读取到的值：{readValue}，信号通信发送存在延迟，正在重试", code);

                    LogManager.Error($"{_logUiChannel}{(code != "" ? ("，产品：" + code) : "")}，在{CurrentStationName}工位下发数据，地址：{addr}，下发值：{value}，读取到的值：{readValue}，信号通信发送存在延迟，正在重试");

                    while (value != readValue) {

                        LogWithUi($"由于网络正在重新下发指令", code);

                        LogManager.Error($"{_logUiChannel}{(code != "" ? ("，产品：" + code) : "")}，在{CurrentStationName}工位，由于网络正在重新下发!");

                        rlt = _client.Write(addr, value).IsSuccess;

                        readValue = _client.ReadString(addr).Content;

                        System.Threading.Thread.Sleep(500);
                    }
                }

                return rlt;
            }

            return false;
        }

        /// <summary>
        /// 获取转化数据
        /// </summary>
        /// <param name="content"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        protected string GetPlcString(byte[] content, short index) {
            byte length = content[index + 1];
            return _client.ByteTransform.TransString(content, index + 2, length, Encoding.ASCII).Replace("\r", "").Replace("\n", "");
        }


        /// <summary>
        /// 生成打印参数
        /// </summary>
        /// <param name="templateEntity">模板</param>
        /// <param name="printStartionCode">打印站点工位枚举</param>
        /// <param name="absScanCode">缸体码</param>
        /// <returns>返回元组，二维码、当前序号、打印参数</returns>
        protected (string qrCode, string curIndex, Dictionary<string, string> printParams) GeneratePrintParams(TemplateEntity templateEntity, ePrintStartionCode printStartionCode, string absScanCode) {

            var reworkCount = _mainDbOperate.GetModelList<T>(o => o.AbsScanCode == absScanCode && o.IsReworked).Count;

            (string qrCode, string curIndex, Dictionary<string, string> printParams) result = default;
            Dictionary<string, string> printParams;
            string curIndex;
            string qrCode = _printerService.GetPrintParams(templateEntity, printStartionCode, out curIndex, out printParams, true, reworkCount);
            #region 确保不重码
            // 获取今天的起始时间（00:00:00）
            var todayStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0);
            // 获取今天的结束时间（23:59:59.999）
            var todayEnd = todayStart.AddDays(1).AddTicks(-1);
            var codeExists = _mainDbOperate.CheckExist<AbsPrintResult>(o => o.Code == qrCode && o.PrintTime >= todayStart && o.PrintTime <= todayEnd);
            var codeExists2 = _mainDbOperate.CheckExist<T>(o => o.Code == qrCode);
            while (codeExists || codeExists2) {
                qrCode = _printerService.GetPrintParams(templateEntity, printStartionCode, out curIndex, out printParams, true, reworkCount, 100);//如果重码，重新生成，当前序号上累加100
                codeExists = _mainDbOperate.CheckExist<AbsPrintResult>(o => o.Code == qrCode && o.PrintTime >= todayStart && o.PrintTime <= todayEnd);
                codeExists2 = _mainDbOperate.CheckExist<T>(o => o.Code == qrCode);
            }
            #endregion

            result.qrCode = qrCode;
            result.curIndex = curIndex;
            result.printParams = printParams;

            return result;
        }

        /// <summary>
        /// 获取当前站打标序号
        /// </summary>
        /// <param name="ePrintStartionCode"></param>
        /// <returns></returns>
        public int GetCurrentLableIndex(ePrintStartionCode ePrintStartionCode) {
            if (Cache.CurWorkProduct != null) {
                var indexModel = _mainDbOperate.GetModel<IndexEntity>(o =>
                        o.ProductID == Cache.CurWorkProduct.Id
                        && o.StationCode == ePrintStartionCode
                        && o.CurentDate == DateTime.Now.ToString("yyyyMMdd"));
                if (indexModel != null) {
                    CurrentLableIndex = indexModel.CurentIndex;
                    return indexModel.CurentIndex;
                }
            }
            return 0;
        }

        private void WorkStationBaseService_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
            try {
                var signalName = e.PropertyName;
                DealWithSignal(signalName);
            } catch (Exception ex) {
                var logChannel = CurrentStationName + "uilog";
                LogWithUi("信号处理异常，异常信息" + ex.Message);
            }
        }

    }

}
